home *** CD-ROM | disk | FTP | other *** search
- ; Play.asm
- ; Emulates Basic's Play commands
- ; Syntax PLAY [string[/K] | filespec/F[/K]]
- ; /K denotes no keyboard polling
- ;
- CODE SEGMENT ;*************************
- ASSUME CS:CODE,DS:CODE ;* *
- ORG 100H ;* REMEMBER TO EXE2BIN *
- ;* *
- START: JMP BEGINNING ;*************************
-
- COPYRIGHT DB 'Copyright l987 Ziff-Davis Publishing Co.',1AH
- PROGRAMMER DB 'Michael J. Mefford'
-
- TIMER_TIC DD ?
-
- OCTAVE DB 4
- LEN DW 64
- TEMPO DW 136
- MUSIC DB 1
- COUNT DW 0
-
- NOTE:
- ; C C# D D# E F
- DW 16744,17736,18792,19912,21096,22352
- ; F# G G# A A# B
- DW 23680,25088,26576,28160,29712,31616
-
- FILE DB 'PLAY.DAT',0
- PARA_FLAG DB 0
- FILE_FLAG DB 0
- POLL_KEY DB 0
-
- ;----------------------------------------------------------;
- ; Speed the clock up to four times normal speed. ;
- ; Capitalize command line and check for switch characters. ;
- ;----------------------------------------------------------;
-
- BEGINNING: MOV BX,16384 ;Divisor for 72.8/sec IRQ 0.
- CALL SET_CLOCK
-
- MOV AX,3508H ;Get INT 8 (timer tic).
- INT 21H
- MOV WORD PTR TIMER_TIC,BX ;Save old vector.
- MOV WORD PTR TIMER_TIC[2],ES
- PUSH DS
- POP ES ;Restore ES.
-
- MOV DX,OFFSET INT_8 ;Point INT 8 to our routine.
- MOV AX,2508H
- INT 21H
-
- CMP BYTE PTR DS:[80H],0 ;Are there any parameters?
- JZ PARAMETERS ;If no, open PLAY.DAT.
-
- CAPITALIZE: MOV SI,81H ;Point to parameters.
- NEXT_CAP: LODSB ;Get a byte.
- CMP AL,13 ;Is it carriage return?
- JZ PARAMETERS ;If yes, done here.
- CMP AL,'/' ;Is it switch character?
- JZ SWITCHES ;If yes, check which one.
- CMP AL,'0' ;Is it a possible command?
- JB NEXT_CAP ;If no, get next byte.
- MOV PARA_FLAG,1 ;Else flag that parameter exists.
- CMP AL,'a' ;Is it lower case?
- JB NEXT_CAP ;If no, get next byte.
- AND BYTE PTR [SI-1],5FH ;Else, capitalize.
- JMP SHORT NEXT_CAP ;Next byte.
-
- SWITCHES: CMP AL,'/' ;Is it switch character?
- JNZ GET_SWITCH ;If no, get next byte.
- MOV BYTE PTR [SI-1],0 ;Else create ASCIIZ for DOS.
- GET_SWITCH: LODSB ;Get a byte.
- CMP AL,13 ;Is it carriage return?
- JZ PARAMETERS ;If yes, done here.
- AND AL,5FH ;Else, capitalize.
- CMP AL,'K' ;Is it /K switch?
- JNZ CK_FILE ;If no, check /F.
- MOV POLL_KEY,1 ;Else, flag as no keyboard poll.
- CK_FILE: CMP AL,'F' ;Is it /F?
- JNZ SWITCHES ;If no, get next byte.
- MOV FILE_FLAG,1 ;Else, flag as filespec.
- JMP SHORT SWITCHES ;Get next byte.
-
- ;-----------------------------------------------------------------------;
- ; Exit is here so it can be reached by as many short jumps as possible. ;
- ; Before terminating, reset clock to 18.2/sec and restore INT 8 vector. ;
- ;-----------------------------------------------------------------------;
-
- EXIT: MOV BX,0 ;Divisor of 65536 or zero.
- CALL SET_CLOCK
-
- MOV DX,WORD PTR TIMER_TIC ;Restore old vector
- MOV AX,WORD PTR TIMER_TIC[2]
- MOV DS,AX
- MOV AX,2508H ; of INT 8.
- INT 21H
-
- INT 20H ;Terminate.
-
- ;-----------------------------------------------------------------;
- ; Check to see if parameters exist and if yes check if filespec. ;
- ; If yes, read file into buffer and process by stripping Wordstar ;
- ; high bit, capitalizing, stripping comments and stripping space ;
- ; characters and below. Finish by tacking on a carriage return. ;
- ;-----------------------------------------------------------------;
-
- PARAMETERS: CMP PARA_FLAG,1 ;Are there parameter?
- JZ FILE_NAME ;If yes, check if filespec.
- MOV DX,OFFSET FILE ;Else, point to PLAY.DAT
- JMP SHORT OPEN_FILE ; and open file.
-
- FILE_NAME: CMP FILE_FLAG,1 ;Is it a filespec?
- JNZ READ_COMMAND ;If no, read the command line.
- MOV DX,82H ;Else, point to filespec.
-
- OPEN_FILE: MOV AX,3D00H ;Open file for reading.
- INT 21H
- JC EXIT ;If not found, exit.
-
- MOV BX,AX ;Else, filehandle in BX.
- MOV DX,OFFSET BUFFER ;Read file into buffer.
- MOV CX,0F000H
- MOV AH,3FH
- INT 21H
-
- MOV CX,AX ;File length in CX.
- MOV SI,OFFSET BUFFER ;Initialized SI and DI
- MOV DI,OFFSET BUFFER ; to head of buffer.
-
- FORMAT: LODSB ;Get a byte.
- AND AL,7FH ;Strip Wordstar high bit.
- CMP AL,':' ;Is it comment character?
- JZ STRIP ;If yes, strip comment.
- CMP AL,'a' ;Is it lower case?
- JB CK_CONTROL ;If no, check space and below.
- AND AL,5FH ;Else, capitalize.
- CK_CONTROL: CMP AL,32 ;Is it space or below?
- JBE NEXT_FORMAT ;If yes, don't store.
- STOSB ;Else store the character.
- NEXT_FORMAT: LOOP FORMAT ;Get next byte.
- END_FORMAT: MOV BYTE PTR [DI],13 ;Tack on carriage return as EOF.
- MOV SI,OFFSET BUFFER ;Point to commands.
- JMP SHORT NEXT_COMMAND ;Get the commands.
-
- NEXT_STRIP: LODSB ;Get a byte.
- AND AL,7FH ;Strip Wordstar high bit.
- CMP AL,10 ;Is it linefeed?
- JZ NEXT_FORMAT ;If yes get next command.
- STRIP: LOOP NEXT_STRIP ;Else, skip and get next byte.
- JMP SHORT END_FORMAT
-
- ;--------------------------------;
- ; This is the command processor. ;
- ;--------------------------------;
-
- READ_COMMAND: MOV SI,82H ;Initialize pointer to commands.
-
- NEXT_COMMAND: CMP POLL_KEY,1 ;Should we poll keyboard?
- JZ GET_COMMAND ;If no, skip.
- MOV AH,1 ;Else, check for keystroke
- INT 16H ; via BIOS.
- JNZ EXIT ;If keystroke, exit.
- GET_COMMAND: LODSB ;Else, get a byte.
- CMP AL,13 ;Is it carriage return?
- JA CONTINUE ;If no, continue.
- JMP EXIT ;Else, we are done.
- CONTINUE: CMP AL,32 ;Is it space, comma or semicolon?
- JZ NEXT_COMMAND ;If yes, skip.
- CMP AL,','
- JZ NEXT_COMMAND
- CMP AL,';'
- JZ NEXT_COMMAND
-
- CMP AL,'O' ;Is it "O" ; Octave?
- JNZ CK_L ;If no, check "L".
- CALL CK_NUMBER ;Else, get number.
- CMP AL,7 ;Is it in range?
- JA NEXT_COMMAND ;If no, skip.
- MOV OCTAVE,AL ;Else store.
- JMP SHORT NEXT_COMMAND
-
- CK_L: CMP AL,'L' ;Is it "L" ; Length?
- JNZ CK_TEMPO ;If no, check tempo.
- CALL CK_NUMBER ;Else, get number.
- CMP AL,0 ;Is it between 1 and 64?
- JZ NEXT_COMMAND ;If no, skip.
- CMP AL,64
- JA NEXT_COMMAND
- XOR DX,DX ;Else, zero in high half.
- MOV AX,256 ;Dividend of 256.
- DIV BX ;Divide by length
- MOV LEN,AX ; and store.
- JMP SHORT NEXT_COMMAND
-
- CK_TEMPO: CMP AL,'T' ;Is it "T" ; tempo?
- JNZ CK_N ;If no, check number.
- CALL CK_NUMBER ;Else, get number.
- CMP AL,32 ;Is it between 32 and 255?
- JB NEXT_COMMAND ;If no, skip.
- MOV AX,255
- SUB AL,BL ;Else, complement.
- INC AX ;Make non zero
- MOV TEMPO,AX ; and store.
- JMP SHORT NEXT_COMMAND
-
- CK_N: CMP AL,'N' ;Is it "N" ; number?
- JNZ CK_NOTE ;If no, check note.
- CALL CK_NUMBER ;Else, get number.
- CMP AL,84 ;Is it between 0 and 84?
- JA NEXT_COMMAND ;If no, skip.
- CMP AL,0
- JZ GOT_N
- DEC AL ;Adjust number.
- GOT_N: XOR AH,AH ;Zero in high half.
- MOV CL,12 ;Divide by 12.
- DIV CL
- MOV CX,8 ;Get octave.
- SUB CL,AL
- MOV BL,AH ;Remainder in BX pointer.
- XOR BH,BH
- SHL BX,1 ;Adjusts as word pointer.
- MOV BX,[OFFSET NOTE+BX] ;Retrieve note.
-
- CALL PLAY_NUMBER ;Play the note.
- JMP NEXT_COMMAND
-
- CK_NOTE: CMP AL,'A' ;Is it letter note ; A-G?
- JB COMMAND_END
- CMP AL,'G'
- JA CK_P ;If no, check pause.
- CMP AL,'C'
- JB ABOVE_C
- SUB AL,7 ;First subtract 7 for C and above.
- ABOVE_C: SUB AL,60 ;Finish pointer adjustment.
- XOR AH,AH ;Zero in high half.
- MOV DI,AX ;Move into pointer.
- MOV CL,2 ;Convert to word pointer
- SHL DI,CL ; by multiplying by 2.
- CMP DI,8 ;Adjust if necessary.
- JBE GOT_NOTE
- SUB DI,2
-
- GOT_NOTE: CALL BLACK_KEY ;Check for sharps or flats.
- CALL PLAY ;Play the note.
- JMP NEXT_COMMAND
-
- CK_P: CMP AL,'P' ;Is it "P" ; pause?
- JNZ CK_K ;If no, check key poll.
- CALL CK_NUMBER ;Else, get number.
- CMP AL,0 ;Is it between 1 and 64?
- JZ COMMAND_END ;If no, skip.
- CMP AL,64
- JA COMMAND_END
- XOR DX,DX ;Else, zero in high half.
- MOV AX,256 ;Dividend of 256.
- DIV BX ;Divide by length
- MOV BP,AX ;Pause in BP.
- CALL PAUSE_DELAY
- JMP NEXT_COMMAND
-
- CK_K: CMP AL,'K' ;Is it "K" ; non keyboard poll?
- JNZ CK_M ;If no, check music.
- MOV POLL_KEY,1 ;Else, flag as no poll.
- JMP NEXT_COMMAND
-
- CK_M: CMP AL,'M' ;Is it "M" ; music?
- JNZ COMMAND_END ;If no, next command.
- LODSB ;Else, get next byte.
- CMP AL,13
- JNZ CK_N2
- JMP EXIT
- CK_N2: CMP AL,'N' ;Is it "N" ; normal?
- JNZ CK_L2 ;If no, check L.
- MOV MUSIC,1 ;Else, flag as music one.
- CK_L2: CMP AL,'L' ;Is it "L" ; legato?
- JNZ CK_S2 ;If no, check S.
- MOV MUSIC,2 ;Else, flag as music two.
- CK_S2: CMP AL,'S' ;Is it "S" ; staccato?
- JNZ COMMAND_END ;If no, skip.
- MOV MUSIC,3 ;Else, flag as music three.
- COMMAND_END: JMP NEXT_COMMAND
-
- ;*************;
- ; Subroutines ;
- ;*************;
-
- ;---------------------------------------------------------;
- ; This subroutine converts decimal command number to hex. ;
- ;---------------------------------------------------------;
-
- CK_NUMBER: XOR BX,BX ;Initialize to zero.
- NEXT_NUMBER: CMP BYTE PTR [SI],'0' ;Is it a number?
- JB END_NUMBER ;If no, we're done.
- CMP BYTE PTR [SI],'9'
- JA END_NUMBER
- LODSB ;Get number.
- SUB AL,30H ;Convert to hex.
- MOV DL,AL
- MOV AX,10 ;Shift decimal by ten.
- MUL BL
- MOV BL,AL ;Result in BL.
- ADD BL,DL ;Add new number.
- JMP SHORT NEXT_NUMBER ;Get next number.
-
- END_NUMBER: MOV AL,BL ;Return with number in AL.
- XOR AH,AH ;Zero in high half.
- RET
-
- ;---------------------------------------------;
- ; This subroutine checks for sharps or flats. ;
- ;---------------------------------------------;
-
- BLACK_KEY: CMP BYTE PTR [SI],'+' ;Is it sharp?
- JNZ CK_SHARP ;If no, check #.
- ADD DI,2 ;Else, point to next note.
- INC SI ;Adjust command pointer.
- CK_SHARP: CMP BYTE PTR [SI],'#' ;Do same for #.
- JNZ CK_FLAT
- ADD DI,2
- INC SI
- CK_FLAT: CMP BYTE PTR [SI],'-' ;Do same for flat except
- JNZ END_BLACK ; decrement note pointer.
- SUB DI,2
- INC SI
- END_BLACK: RET
-
- ;---------------------------------;
- ; This subroutine plays the note. ;
- ;---------------------------------;
-
- PLAY: MOV BX,[OFFSET NOTE+DI] ;Retrieve the note.
- MOV CX,8 ;Complement the octave.
- SUB CL,OCTAVE
- PLAY_NUMBER: SHR BX,CL ;Divide by octave base two.
- MOV DX,12H ;120000h dividend constant.
- XOR AX,AX
- DIV BX ;Divide by frequency.
- MOV BX,AX ;Store in BX.
- MOV AL,0B6H ;Latch to Channel 2.
- OUT 43H,AL
- MOV AX,BX ;Send LSB then MSB to 8253.
- OUT 42H,AL
- MOV AL,AH
- OUT 42H,AL
- IN AL,61H
- OR AL,3 ;Turn bits 0 and 1 on 8255 chip
- OUT 61H,AL ; to turn speaker on.
-
- CALL DELAY ;Delay.
-
- OFF: IN AL,61H ;Turn bits 0 and 1 back off.
- AND AL,11111100B
- OUT 61H,AL
-
- MOV CX,BX ;Delay for pause between notes.
- CALL STACCATO
-
- RET
-
- ;------------------------------------------------;
- ; This subroutine calculates the length of play. ;
- ;------------------------------------------------;
-
- DELAY: MOV BP,LEN ;Retrieve length.
- PAUSE_DELAY: MOV CX,BP ;Store in CX.
- NEXT_DOT: CMP BYTE PTR [SI],'.' ;Is it a dotted note?
- JNZ GOT_DELAY ;If no, got length.
- INC SI ;Else, adjust command pointer.
- SHR CX,1 ;Divide length by two and
- ADD BP,CX ; add to total length.
- JMP SHORT NEXT_DOT ;Check for another dot.
-
- GOT_DELAY: MOV CX,BP ;Length in CX.
- PUSH CX ;Save.
- CMP MUSIC,2 ;Is it music legato?
- JZ WAIT ;If yes, play full length.
-
- MOV AX,CX ;Else, length in AX.
- XOR DX,DX ;Zero in high half.
- CMP MUSIC,1 ;Is it music normal?
- JNZ MUSIC_3 ;If no, must be music stacatto.
- MOV CX,7 ;Multiply by 7/8.
- MUL CX
- MOV CL,3
- SHR AX,CL
- MOV CX,AX
- JMP SHORT WAIT
-
- MUSIC_3: MOV CX,3 ;Multiply by 3/4.
- MUL CX
- MOV CL,2
- SHR AX,CL
- MOV CX,AX
-
- WAIT: POP BX ;Recover total length.
- SUB BX,CX ;Subtract pause time.
-
- STACCATO: MOV AX,TEMPO ;Retrieve tempo.
- MUL CL ;Multiply by length of note.
- XOR DX,DX
- MOV CX,200 ;Adjust by looping constant.
- DIV CX
- INC AX ;Prevent zero loop.
-
- MOV CX,COUNT ;Get present counter.
- ADD CX,AX ;Add length of note.
- JB END_DELAY ;Skip if count wrapped.
- NEXT_DELAY: MOV AX,COUNT ;Get counter again.
- CMP AX,CX ;Has delay expired?
- JB NEXT_DELAY ;If no, get counter until it does.
- END_DELAY: RET
-
- ;------------------------------------------------------;
- ; This subroutine programs Channel 0 of the 8253 chip. ;
- ;------------------------------------------------------;
-
- SET_CLOCK: MOV AL,00110110B ;Latch to Channel 0.
- OUT 43H,AL ;Send LSB then MSB.
- MOV AL,BL
- OUT 40H,AL
- MOV AL,BH
- OUT 40H,AL
- RET
-
- ;------------------------------------;
- ; This is the new INT 8 interceptor. ;
- ;------------------------------------;
-
- ASSUME DS:NOTHING
-
- INT_8: STI ;Interrupts back on.
- PUSHF ;Save registers.
- PUSH AX
- PUSH CX
- PUSH DX
- INC COUNT ;Increment our counter.
- MOV AX,COUNT ;Check if divisible by 4.
- XOR DX,DX
- MOV CX,4
- DIV CX
- CMP DX,0
- JNZ TIMER_RET ;If no, skip clock update.
- POP DX ;Else, restore registers.
- POP CX
- POP AX
- POPF
- JMP TIMER_TIC ;Pass control to old INT 8.
-
- TIMER_RET: POP DX ;Restore DX and CX; need AX still.
- POP CX
- CLI ;No interrupts.
- MOV AL,20H ;Send 8259 command port EOI
- OUT 20H,AL ; end-of-interrupt.
- POP AX ;Restore other registers.
- POPF
- IRET ;Interrupt return.
-
- BUFFER:
-
- CODE ENDS
- END START
-
-
-
-